import socket
import argparse
import inspect
import ipaddress
import json
import select
from socket import *

# 该类包含服务端所有可以用于服务的注册函数，初始12个，可以继续增加
class funSupport:
    def sortedList(list):
        return sorted(list)
    def isEven(a):
        if a % 2 == 1:
            return True
        else:
            return False
    def add(a, b):
        return a + b
    def sub(a, b):
        return a - b
    def mul(a, b):
        return a * b
    def div(a, b):
        return a / b
    def mod(a, b):
        return a % b
    def power(a, b):
        return a**b
    def square_root(a):
        return a ** 0.5
    def maximum(list):
        return max(list)
    def minimum(numbers):
        return min(numbers)
    def square(a):
        return a * a

# RPC服务端的实现类
class RPCServer:
    def __init__(self):
        self.funcSav = {}

    # 服务注册
    def regFunc(self, func):
        # 将函数名作为键,函数本体为值以字典
        self.funcSav[func.__name__] = func

    # 服务发现
    def getFunc(self):
        return list(self.funcSav.keys())

    # RPC请求的处理
    def sovFunc(self, connect):
        connect.settimeout(8)  # 设置超时时间为8s
        try:
            data = connect.recv(1024)  # 从与客户端连接的套接字中接收数据
        except timeout:
            # 处理超时异常
            print("请求超时")
            connect.close()
        except Exception as e:
            print("读取数据失败: {}".format(str(e)))
            connect.close()
        if not data:
            print("没有输入数据")
            connect.close()
            return
        # 获取数据长度 即数据头部
        mesLen = int.from_bytes(data[:4], byteorder='big')
        if len(data) < mesLen + 4:
            print("数据头提示长度小于实际接受的数据体长度")
            connect.close()
            return

        mes = json.loads(data[4:4 + mesLen].decode())
        # 获取函数名和位置参数
        funName = mes['func']
        if funName == 'getFunc':
            result = self.getFunc()
        elif funName in self.funcSav:
            # 处理已注册的函数的请求
            args = mes['args']  # 从解析后的对象中获取位置参数

            # 对于客户端调用函数时参数数量不一致的检测
            funP = inspect.signature(self.funcSav[funName]).parameters.values()  # 获取注册函数的参数字典中的值部分
            if len(args) != len(funP):
                result = '调用的函数{}的参数数量不正确,需要输入{}个参数'.format(funName, len(funP))
            else:
                try:
                    for arg, param in zip(args, funP):
                        # 获取函数参数的预期类型
                        expectType = param.annotation if param.annotation != inspect.Parameter.empty else None
                        argType = type(arg) if expectType is not None else None
                        # 检查实际参数的类型是否与预期类型匹配
                        if expectType != argType:
                            # 参数类型不匹配，抛出类型错误异常
                            raise TypeError(
                                "参数类型不匹配: 期望 {} , 输入 {} ".format(expectType.__name__, argType.__name__))
                    # 要返回给客户端的调用结果
                    result = '{}的调用结果: {}'.format(funName,self.funcSav[funName](*args))

                except TypeError as e:
                    # 处理参数类型错误的异常
                    err = str(e)
                    result = {'error': '参数类型错误: {}'.format(err)}  # 返回错误信息给客户端

                except Exception as e:
                    # 处理其他异常
                    err = str(e)
                    result = {'error': '处理数据时出现异常: {}'.format(err)}  # 返回错误信息给客户端

        else:
            # 处理未注册的函数的请求
            result = '调用的函数 {} 未在服务端注册'.format(funName)

        # 将结果封装为JSON格式并编码为字节后发送给客户端
        try:
            resData = json.dumps({'result': result}).encode()
            connect.sendall(len(resData).to_bytes(4, byteorder='big') + resData)

        except timeout:
            # 处理发送超时异常
            print("发送响应数据超时")
            connect.close()
        except Exception as e:
            print("发送响应数据失败: {}".format(str(e)))
            connect.close()

    # 服务端启动函数
    def runServer(self, ip, port):
        # 运行RPC服务器
        if ipaddress.ip_address(ip).version == 6:
            # ip = "[" + ip + "]"
            SSoc = socket(AF_INET6, SOCK_STREAM)
        else:
            SSoc = socket(AF_INET, SOCK_STREAM)

        SSoc.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)    # 端口重用

        SSoc.bind((ip, port))    # 绑定端口号和套接字
        SSoc.setblocking(False)  # 非阻塞模式
        SSoc.listen(1024)   # 设置最大连接数

        # 创建epoll对象
        epoll = select.epoll()
        epoll.register(SSoc.fileno(), select.EPOLLIN)
        print("RPC服务器运行ing... ip:{} port:{}".format(ip, port))

        # 存储套接字和客户端地址的字典
        SocList = {}
        addressList = {}
        while True:
            try:
                while True:
                    # 检测并获取epoll监控的已触发事件
                    epollList = epoll.poll()
                    for fd, events in epollList:
                        # 如果有新的连接请求
                        if fd == SSoc.fileno():
                            newSoc, clientAddr = SSoc.accept()
                            newSoc.setblocking(False)  # 非阻塞模式
                            print('有新的客户端到来:%s' % str(clientAddr))
                            # 为新套接字的文件描述符注册读事件
                            epoll.register(newSoc.fileno(), select.EPOLLIN)
                            # 将新套接字和客户端地址保存到字典中
                            SocList[newSoc.fileno()] = newSoc
                            addressList[newSoc.fileno()] = clientAddr
                        # 如果监听到 EPOLLIN 事件, 表示对应的文件描述符可以读
                        elif events & select.EPOLLIN:
                            # 获取套接字并调用处理函数
                            connect = SocList[fd]
                            self.sovFunc(connect)
                        elif events & select.EPOLLHUP:
                            print(f"客户端 {fd} 断开连接")
                            epoll.unregister(fd)
                            connect = SocList[fd]
                            print('客户端断开连接：%s' % str(connect.getpeername()))
                            connect.close()
            except KeyboardInterrupt:
                print("RPC服务器已关闭")
            finally:
                epoll.unregister(SSoc.fileno())
                epoll.close()
                SSoc.close()

# 服务端的启动参数
def serverStart():
    parser = argparse.ArgumentParser(description='服务端启动参数帮助')
    parser.add_argument('-l','--listen',required=True,help='监听IP不得为空')
    parser.add_argument('-p','--port',required=True,help='监听端口号不能为空')
    return parser.parse_args()

if __name__ == '__main__':
    args = serverStart()
    lisIp = args.listen
    port = args.port

    rpcServer = RPCServer()

    # 测试用例：注册受支持的函数
    rpcServer.regFunc(funSupport.sortedList)
    rpcServer.regFunc(funSupport.add)
    rpcServer.regFunc(funSupport.div)
    rpcServer.regFunc(funSupport.minimum)
    rpcServer.regFunc(funSupport.maximum)

    rpcServer.runServer(lisIp, int(port))
    
